package cn.bingoo.handy;

import java.awt.Color;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.swing.JTextPane;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;

import net.sf.commons.ssh.AuthenticationOptions;
import net.sf.commons.ssh.Connection;
import net.sf.commons.ssh.ConnectionFactory;
import net.sf.commons.ssh.PasswordAuthenticationOptions;
import net.sf.commons.ssh.ShellSession;
import net.sf.commons.ssh.ShellSessionOptions;
import cn.bingoo.lang.Ios;
import cn.bingoo.lang.Ofs;
import cn.bingoo.lang.Tuple4;
import cn.bingoo.swing.utils.Colors;
import cn.bingoo.swing.utils.Swings;

public class SshTuple {
    private Tuple4<Connection, ShellSession, Reader, PrintStream> shellTuple;
    private Style darkBlueStyle;
    private Style backStyle;
    private Style redStyle;
    private JTextPane textResult;
    Map<String, String> params;

    public SshTuple(JTextPane textResult) {
        this.textResult = textResult;
        darkBlueStyle = textResult.addStyle("DARK_BLUE", null);
        StyleConstants.setForeground(darkBlueStyle, Colors.DARK_BLUE);
        StyleConstants.setBold(darkBlueStyle, true);
        backStyle = textResult.addStyle("BLACK", null);
        StyleConstants.setForeground(backStyle, Color.BLACK);
        redStyle = textResult.addStyle("RED", null);
        StyleConstants.setForeground(redStyle, Color.RED);
    }

    public void execCmds(String configLines, String textCmds) {
        String[] configLinesArr = configLines.split("\n");
        for (String configLine : configLinesArr) {
            configLine = configLine.trim();
            if (configLine.indexOf('#') == 0) {
                continue;
            }
            String[] columns = configLine.split(",");
            if (columns.length < 3) {
                continue;
            }

            SshConfig sshConfig = new SshConfig();
            sshConfig.setIp(columns[0].trim());
            sshConfig.setLoginName(columns[1].trim());
            sshConfig.setPassword(columns[2].trim());

            Swings.appendText(textResult, "\r\n>>>using " + sshConfig.getIp() + "\r\n", redStyle);
            execCmds(textCmds, sshConfig, "$");
        }
    }

    private Pattern expectPattern = Pattern.compile("^expect\\s+(.*)$", Pattern.CASE_INSENSITIVE);
    private Pattern paramPattern = Pattern.compile("^param\\s+(\\w+)\\s*=\\s*(\\w+)\\s*$", Pattern.CASE_INSENSITIVE);

    private void execCmds(String textCmds, SshConfig sshConfig, String terminator) {
        try {
            params = new HashMap<String, String>();
            shellTuple = createShellTuple(sshConfig);
            String[] cmds = textCmds.split("\n");
            for (String cmd : cmds) {
                cmd = cmd.trim();
                if (cmd.indexOf('#') == 0) {
                    // ignore comments
                    continue;
                }

                if (cmd.indexOf("bingoo ") == 0) {
                    processBingooInstructions(cmd);
                }
                else {
                    processNormalInstructions(terminator, cmd);
                }
            }
        }
        catch (Exception ex) {
            Swings.appendText(textResult, "Exception!" + ex.toString() + "\r\n", redStyle);
            ex.printStackTrace();
        }
        finally {
            closeShellTuple();
            shellTuple = null;
            params = null;
        }
    }

    private void processNormalInstructions(String terminator, String cmd)
            throws Exception {
        Swings.appendText(textResult, "$" + cmd + "\r\n", darkBlueStyle);
        shellTuple._4.print(cmd + '\n');
        shellTuple._4.flush();
        if (!"false".equals(params.get("AutoExpectTerminator"))) {
            getServerResponse(terminator);
        }
    }

    private void processBingooInstructions(String cmd) throws Exception {
        String bingooCmd = cmd.substring("bingoo ".length());
        bingooCmd = bingooCmd.trim();
        Matcher matcher = expectPattern.matcher(bingooCmd);
        if (matcher.matches()) {
            getServerResponse(matcher.group(1));
        }
        else {
            matcher = paramPattern.matcher(bingooCmd);
            if (matcher.matches()) {
                params.put(matcher.group(1), matcher.group(2));
            }
        }
    }

    private Tuple4<Connection, ShellSession, Reader, PrintStream> createShellTuple(SshConfig sshConfig)
            throws IOException, UnsupportedEncodingException {
        Tuple4<Connection, ShellSession, Reader, PrintStream> shellTuple;
        Set<String> requiredFeatures = new HashSet<String>();
        ConnectionFactory connectionFactory = ConnectionFactory.newInstance(
                "net.sf.commons.ssh.jsch.JschConnectionFactory", requiredFeatures);
        AuthenticationOptions authOptions = new PasswordAuthenticationOptions(sshConfig.getLoginName(),
                sshConfig.getPassword());
        shellTuple = Ofs.of((Connection) null, (ShellSession) null, (Reader) null, (PrintStream) null);
        shellTuple._1 = connectionFactory.openConnection(sshConfig.getIp(), authOptions);
        ShellSessionOptions shellSessionOptions = new ShellSessionOptions();
        shellTuple._2 = shellTuple._1.openShellSession(shellSessionOptions);
        shellTuple._3 = new InputStreamReader(shellTuple._2.getInputStream(), "UTF-8");

        shellTuple._4 = new PrintStream(shellTuple._2.getOutputStream());
        return shellTuple;
    }

    private void getServerResponse(String terminator) throws IOException, InterruptedException {
        long startTime = System.currentTimeMillis();
        final char[] buffer = new char[0x10000];
        Reader in = shellTuple._3;
        while (System.currentTimeMillis() - startTime < 2000) {
            if (in.ready()) {
                int read = in.read(buffer, 0, buffer.length);
                if (read > 0) {
                    String str = new String(buffer, 0, read);

                    Swings.appendText(textResult, str, (Style) null);
                    // System.err.println(str);
                    if (terminator != null && terminator.length() > 0 && str.indexOf(terminator) >= 0) {
                        return;
                    }
                }
            }
            else {
                Thread.sleep(100);
                // System.err.println("Thread.sleep(500)");
            }
        }
        // System.err.println("end...");
    }

    private void closeShellTuple() {
        if (shellTuple != null) {
            Ios.closeQuietly(shellTuple._4, shellTuple._3);
            closeQuietly(shellTuple._2);
            closeQuietly(shellTuple._1);
        }
    }

    private void closeQuietly(Connection connection) {
        if (connection != null) {
            try {
                connection.close();
            }
            catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }

    private void closeQuietly(ShellSession shellSession) {
        if (shellSession != null) {
            try {
                shellSession.close();
            }
            catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }

}
